package com.onpositive.knowledge.model.value.audits;

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import com.onpositive.data.IPropertyConstants;
import com.onpositive.datamap.core.IReference;
import com.onpositive.inferencer.AbstractTheory;
import com.onpositive.inferencer.HasPropertyTheory;
import com.onpositive.units.ParsedIdent;
import com.onpositive.units.ParsedLinkArray;
import com.onpositive.units.ParsedString;
import com.onpositive.units.ParsedValue;
import com.onpositive.units.ValueCluster;
import com.onpositive.utils.DataLocationProvider;
import com.onpositive.inferencer.ValueMapTheory;
import com.onpositive.knowledge.model.values.DescribedValue;
import com.onpositive.knowledge.model.values.ReplacementProposals;
import com.onpositive.knowledge.model.values.ValueTraverser;
import com.onpositive.semantic.model.api.property.IProperty;

public class PredictionAudit extends AuditSourceContainer implements IUniverseAudit {

	/**
	 * 
	 */
	private static final long serialVersionUID = 3051444410860830818L;
	public static final String ID = "prediction-audit" ;

	@Override
	public void perform(Iterable<IProperty> properties,	Iterable<Object> objects, AbstractAuditResult auditResult) {
		
		if( !(auditResult instanceof OwnAuditResult) )
			return ;
		
		HashMap<String, IProperty> specialProperties = collectSpecialProperties(properties);
		IProperty documentNameProperty = specialProperties.get(IPropertyConstants.DOCUMENT_NAME_PROPERTY_METADATA_ID) ;	
		IProperty documentActualityProperty = specialProperties.get(IPropertyConstants.ACTUALITY_PROPERTY_METADATA_ID ) ;
		
		ArrayList<Object> actualObjects = collectActualObjects( objects, documentActualityProperty ) ;
		OwnAuditResult ownResult = (OwnAuditResult) auditResult ;		
		

		
		for ( IProperty prop : properties ) {			
			
			
			
			Boolean isService = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_IS_SERVICE_PROPERTY, Boolean.class, null) ;
			if( isService != null && isService )
				continue ;
			
			Boolean isDepricated = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_DEPRICATED_LABEL, Boolean.class, null);
			if( isDepricated != null && isDepricated )
				continue ;
			
			Boolean isIdent = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_IS_IDENTIFICATOR, Boolean.class, null);
			if( isIdent != null && !isIdent )
				continue ;
			
			ArrayList<ValueMapTheory> theoriesList = buildTheories( prop, actualObjects, properties, specialProperties ) ;
			
			if ( theoriesList.isEmpty() )
				continue ;
			
			ArrayList<Integer> theoryRates = new ArrayList<Integer>( theoriesList.size() ) ;
			for( AbstractTheory th : theoriesList )
				theoryRates.add( th.getRating(objects) ) ;
			
			int theoriesCount = theoriesList.size() ;
			 
			StringBuilder logBuilder = new StringBuilder() ;  
			for ( Object o : actualObjects ) {
				
				
//				Collection<Object> collection2 = ValueUtils.toCollection(PropertyAccess.getValues(prop, o));
//				
//				if ( !(collection2 == null || collection2.isEmpty()) )
//					continue ;
				
				for ( int i = 0 ; i < theoriesCount ; i++ )
				{
					AbstractTheory mq = theoriesList.get(i) ;
					int theoryWeight = theoryRates.get(i) ;  
					Object value = mq.getValue(o);
					Object docName = documentNameProperty.getValue(o) ;
					Integer weight = 0 ;
					if( mq instanceof ValueMapTheory )
						weight = ((ValueMapTheory)mq).getObjectRating(o) ;
						
					
					if (value != null) {
						StringBuilder bld= new StringBuilder( prop.getId() ) ;
						bld.append(":") ;
						bld.append(docName) ;
						bld.append("=") ;
						bld.append(value) ;
						bld.append(":(") ;
						bld.append(mq) ;
						bld.append(")" ) ;
						bld.append("; th.w=") ;
						bld.append( theoryWeight ) ;
						if( weight != null ){
							bld.append("; obj.w=") ;
							bld.append(weight) ;
						}
						bld.append("( value class=") ;
						bld.append( value.getClass().getSimpleName() ) ;
						bld.append(")") ;
						
						String logString = bld.toString() ;
						//System.out.println( logString );
						logBuilder.append(logString) ;
						logBuilder.append("\n") ;
					}
				}				
			}
			saveLogs( logBuilder, prop.getId() ) ;
			logBuilder.delete( 0,logBuilder.length() ) ;
			
			HashMap<Object,TheoryAssembly> bestValueMap = buildBestMap ( actualObjects, theoriesList ) ;
			HashMap<ValueMapTheory,Integer> theoryWeightMap = getWeights( actualObjects, theoriesList ) ;
			PropertyResult pRes = createPropertyResult( prop, bestValueMap, theoryWeightMap ) ;
			if( pRes != null ){
				ownResult.addPorpertyResult(prop, pRes) ;
				auditResult.setLooksApplyable(true) ;
			}
		}		
	}



	private ArrayList<Object> collectActualObjects(Iterable<Object> _objects, IProperty documentActualityProperty) {

		ArrayList<Object> result = new ArrayList<Object>() ;
		for( Object obj : _objects )
		{
			Object isActual = documentActualityProperty.getValue(obj) ;
			if( isActual != null && isActual.toString().equals(  "false" ) )
				continue ;
			
			result.add(obj) ;
		}
		return result;
	}



	private HashMap<String, IProperty> collectSpecialProperties(
			Iterable<IProperty> properties) {
		HashMap<String,IProperty> specialProperties = new HashMap<String, IProperty>() ;
		
		for( IProperty prop : properties ){
			if( prop.getId().equals(IPropertyConstants.DOCUMENT_NAME_PROPERTY_METADATA_ID ) )
				specialProperties.put(prop.getId(), prop) ;				
			else if( prop.getId().equals( IPropertyConstants.PARENT_CATEGORIES_PROPERTY_NAME ) )
				specialProperties.put(prop.getId(), prop) ;		
			else if(prop.getId().equals( IPropertyConstants.ACTUALITY_PROPERTY_METADATA_ID ) )
				specialProperties.put(prop.getId(), prop) ;
		}
		return specialProperties;
	}
	
	

	private PropertyResult createPropertyResult(
			IProperty prop,
			HashMap<Object, TheoryAssembly> bestValueMap,
			HashMap<ValueMapTheory, Integer> theoryWeightMap) {
		
		ReplacementProposals proposals = new ReplacementProposals(prop) ;
		
		if( bestValueMap == null || bestValueMap.size() == 0 )
			return null ;
		
		for( Map.Entry<Object, TheoryAssembly> entry : bestValueMap.entrySet() ){		
			
			Object object = entry.getKey() ;		
			TheoryAssembly tAss = entry.getValue() ; 
					
			if( tAss == null )
				return null ;
			
			Object newValue = tAss.getValue() ; 
			if( newValue == null )
				return null ;
			
			ArrayList<AbstractTheory> theories = tAss.getTheories();
			if( theories.size() == 1 ){
				int w2 = theoryWeightMap.get( theories.get(0) ) ;
				if( w2 < 0 )
					return null ;
			}
			int totalRate = tAss.getRate();
			if( totalRate < 21 )
				return null ;
			
			StringBuilder bld = new StringBuilder( ID ) ;
			bld.append("; total.w=") ;		
			bld.append( totalRate ) ;
			for( AbstractTheory th : theories ){
				bld.append("; id=") ;
				bld.append( th.getId() ) ;
				bld.append(":th.w=") ;
				bld.append( theoryWeightMap.get( th ) ) ;
				
				if( th instanceof ValueMapTheory ){
					bld.append(":val.w=") ;
					ValueMapTheory vmt = (ValueMapTheory) th ;				
					bld.append( vmt.getObjectRating(object) );
				}
			}		
			String comment = bld.toString() ;
			//FIXME
			if (newValue!=null&&!newValue.toString().contains("[")){
				IReferenceLookup service = prop.getMeta().getService(IReferenceLookup.class);
				if (service!=null){
					Object vl=service.lookupRefs(newValue);
					if (vl!=null){
						newValue=vl;
					}
				}
			}
			DescribedValue dVal = new DescribedValue( newValue , comment ) ;
			proposals.takeProposal(object, dVal, totalRate ) ;		
		}
		if( proposals.isEmpty() )
			return null ;
		
		return new PropertyResult(proposals);
	}



	private void saveLogs(StringBuilder logBuilder, String propName) {
		
		File dirFile = DataLocationProvider.getDataLocation("Infering_logs/" ) ;		
		dirFile.mkdirs() ;
		File file = new File( dirFile, propName.replace(' ', '_' ) + ".txt" ) ;
		
		try {			
			file.createNewFile() ;
			
			PrintStream ps = new PrintStream(file) ;
			ps.print( logBuilder.toString() ) ;
			ps.close() ;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 		
		
	}



	private HashMap<ValueMapTheory, Integer> getWeights(
			Iterable<Object> objects, ArrayList<ValueMapTheory> theoriesList) {
		
		HashMap<ValueMapTheory,Integer> result = new HashMap<ValueMapTheory, Integer>() ;
		for( ValueMapTheory vmt : theoriesList )
		{
			int w = vmt.getRating(objects) ;
			result.put(vmt, w) ;
		}
		return result;
	}



	private HashMap<Object, TheoryAssembly> buildBestMap( Iterable<Object> objects, ArrayList<ValueMapTheory> theoriesList)
	{
		HashMap<Object,TheoryAssembly> resultMap = new HashMap<Object,TheoryAssembly>() ;
		
		int size = theoriesList.size() ;
		ArrayList<Integer> theoryRates = new ArrayList<Integer>( theoriesList.size() ) ;
		for( AbstractTheory th : theoriesList )
			theoryRates.add( th.getRating(objects) ) ;

		HashMap<Object,TheoryAssembly> map = new HashMap<Object, PredictionAudit.TheoryAssembly>() ;
		for ( Object o : objects ) {	
			
			map.clear() ;
			for ( int i = 0 ; i < size ; i++)
			{
				ValueMapTheory th = theoriesList.get(i) ;
				int thRate = theoryRates.get(i) ;
				if(thRate < 0)
					continue ;
				
				Object value = th.getValue(o) ;
				if( value == null  )
					continue ;
				
				int objRate = th.getObjectRating(o) ;
				if( objRate < 0 )
					continue ;
				
				int rate = objRate + thRate ;
				
				TheoryAssembly tArr = map.get(value) ;
				if( tArr == null ){
					tArr = new TheoryAssembly( value ) ;
					map.put( value, tArr) ;
				}
				tArr.theories.add(th) ;
				tArr.rate += rate ;				
			}
			if( map.isEmpty() )
				continue ;
			
			int bestRate = Integer.MIN_VALUE ;
			TheoryAssembly bestArray = null ;
			Object bestValue = null ;
			for( Map.Entry<Object,TheoryAssembly> entry : map.entrySet() )
			{
				TheoryAssembly tArr = entry.getValue() ;
				if( tArr.rate < bestRate )
					continue ;
				
				bestArray = tArr ;
				bestRate = tArr.rate ;
			}
			
			//iterate through map
			
			if( bestArray == null )
				continue ;
			
			resultMap.put(o, bestArray) ;
		}		
		return resultMap ;		
	}
	
	public final static class TheoryAssembly implements Serializable{
		
		private static final long serialVersionUID = -1357338679894791400L;

		private TheoryAssembly( Object value ){
			this.value = value ;
		}
		
		final Object value ;
		int rate = 0 ;
		ArrayList<AbstractTheory> theories = new ArrayList<AbstractTheory>() ;
		
		public int getRate() {
			return rate;
		}

		public ArrayList<AbstractTheory> getTheories() {
			return theories;
		}

		public Object getValue() {
			return value;
		}
	}

	private static ArrayList<ValueMapTheory> buildTheories(
			IProperty targetProp,
			Iterable<Object> objects,
			Iterable<IProperty> properties,
			HashMap<String, IProperty> specialProperties )
	{		
		ArrayList<ValueMapTheory> theoriesList = new ArrayList<ValueMapTheory>();
		ValueMapTheory rs =null;
		for ( IProperty prop : properties )
		{
			//TODO remove stuff
//			if( !prop.getId().equals("manufacturer") )
//				continue ;
			
			Boolean isService = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_IS_SERVICE_PROPERTY, Boolean.class, null) ;
			if( isService != null && isService )
				continue ;
			
			Boolean isDepricated = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_DEPRICATED_LABEL, Boolean.class, null);
			if( isDepricated != null && isDepricated )
				continue ;
			
			if ( prop == targetProp )
				continue ;
			
			Boolean isIdent = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_IS_IDENTIFICATOR, Boolean.class, null);
			
			
			if ( isIdent != null && isIdent )
			{
				rs = new OwnValueMapTheory(targetProp, prop, true,new OwnIdentTraverser(), new OwnIdentTraverser() );
				if (rs.check(objects)) 
					theoriesList.add( rs ) ;
			}
			//WARNING !!! OwnValuePresenceIndicatingTheory now works only with Idents and ParsedStrings
			if (isIdent){
			rs = new OwnValuePresenceIndicatingTheory(targetProp, prop, false);
			if (rs.check(objects))
				theoriesList.add( rs ) ;
			}
		}
		
		IProperty documentNameProperty = specialProperties.get(IPropertyConstants.DOCUMENT_NAME_PROPERTY_METADATA_ID) ;
		IProperty parentCategoriesProperty = specialProperties.get(IPropertyConstants.PARENT_CATEGORIES_PROPERTY_NAME) ;
		
		rs = new OwnValueMapTheory(targetProp, documentNameProperty, true,new OwnIdentTraverser(), new OwnDocumentNameTraverser() );
		if (rs.check(objects)){ 
			theoriesList.add( rs ) ;
		}
		
		rs = new OwnValueMapTheory(targetProp, parentCategoriesProperty, true,new OwnIdentTraverser(), new OwnParentCategoriesTraverser() );
		if (rs.check(objects)){ 
			theoriesList.add( rs ) ;
		}
		
		return theoriesList;
	}

	@Override
	public AbstractAuditResult createAuditResult() {
		return new OwnAuditResult( this );
	}

	@Override
	public String getId() {		
		return ID;
	}
	
	public static class OwnAuditResult extends AbstractAuditResult
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = -5211644672915938113L;
		private HashMap<String,PropertyResult> propertyResultsMap = new HashMap<String, PropertyResult>() ;
		
		public OwnAuditResult( IUniverseAudit audit ) {
			super(audit) ;
		}

		public void addPorpertyResult( IProperty prop, PropertyResult res )
		{
			propertyResultsMap.put( prop.getId(), res ) ;
		}

		public PropertyResult getPropertyResult( String pName ) {
			return propertyResultsMap.get(pName);
		}
	}
	
	public static class PropertyResult extends AbstractAuditResult {
		
		/**
		 * 
		 */
		private static final long serialVersionUID = -1205357621642801106L;
		final ReplacementProposals proposals ;

		public PropertyResult( ReplacementProposals proposals  ) {
			super();
			this.proposals = proposals ;			
		}

		public ReplacementProposals getProposals() {
			return proposals;
		}
	}
	
	private static class OwnValueMapTheory extends ValueMapTheory{

		public OwnValueMapTheory(IProperty about, IProperty dependent, boolean sa, OwnAbstractTraverser<?> tt, OwnAbstractTraverser<?> st) {
			super(about, dependent, sa);
			ownTargetTraverser = tt ;
			ownSourceTraverser = st ;
		}
		
		private final OwnAbstractTraverser<?> ownTargetTraverser ;
		private final OwnAbstractTraverser<?> ownSourceTraverser ;
		
		@Override
		public Iterable<? extends Object> doTargetGet(Object sample, IProperty property )
		{
			ownTargetTraverser.itereteObjectValues( property, sample, new HashMap<String,Object>(), 0 ) ;
			return ownTargetTraverser.getStringValues() ;
		}
		
		@Override
		public Iterable<? extends Object> doSourceGet(Object sample, IProperty property )
		{
			ownSourceTraverser.itereteObjectValues( property, sample, new HashMap<String,Object>(), 0 ) ;
			return ownSourceTraverser.getStringValues() ;
		}

		@Override
		protected String getClassId() {
			return "ATh";//Audit theory
		}	
	}
	
	private final static class OwnValuePresenceIndicatingTheory extends OwnValueMapTheory{
		
		public OwnValuePresenceIndicatingTheory(IProperty about, IProperty dependent, boolean sa)
		{			
			super(about, dependent, sa, new OwnIdentTraverser(), new OwnIdentTraverser());
		}

		@Override
		public Iterable<? extends Object> doSourceGet(Object sample, IProperty property )
		{
			Iterable<? extends Object> val = super.doSourceGet(sample, property) ;
			return val != null ? Collections.singleton( Boolean.TRUE ) : null ;
		}
		@Override
		protected String getClassId() {
			return "PTh";//value Presence theory
		}	
	
	}
	
	private static final String ALREADY_GOT_IDENT_CONST = "already-got-ident";

	static abstract class OwnAbstractTraverser<T> extends ValueTraverser{		
		public abstract ArrayList<T> getStringValues() ;	
	}
	
	private static final class OwnIdentTraverser extends OwnAbstractTraverser<ParsedValue>{		

		private ArrayList<ParsedValue> stringValues = new ArrayList<ParsedValue>() ;
		
		@Override
		public ArrayList<ParsedValue> getStringValues(){
			
			if( stringValues.isEmpty() )
				return null ;
			
			ArrayList<ParsedValue> result = new ArrayList<ParsedValue>( stringValues ) ;
			stringValues.clear() ;
			return result ;
		}

		@Override
		protected void process( IProperty prop, Object val, Map<String, Object> metaInfo, int index ) {
			
			if( val instanceof ParsedString )
			{
				Boolean alreadyGotIdent = (Boolean) metaInfo.get(ALREADY_GOT_IDENT_CONST) ;
				if( alreadyGotIdent != null && alreadyGotIdent )
					return ;
				
				ParsedString ps = (ParsedString) val ;
				String valueString = (String) ps.value() ;
				if( valueString == null || valueString.length() == 0 )
					return ;
				
				stringValues.add( ps ) ;
			}
			else if( val instanceof ParsedIdent )
			{
				ParsedIdent pi = (ParsedIdent) val ;
				processIdent(pi, metaInfo);				
			}
			else if( val instanceof ValueCluster )
			{
				ValueCluster vc = (ValueCluster) val ;
				if( vc.getTargetClass() != ParsedIdent.class )
					return ;
				
				ValueCluster<ParsedIdent> identCluster = vc ;				
				ParsedIdent pi = identCluster.getTargetValue() ;
				
				processIdent(pi,metaInfo);				
			}			
		}
		
		

		private final void processIdent(ParsedIdent pi,Map<String, Object> metaInfo)
		{			
			String ref = pi.getRef() ;
			if( ref == null || ref.length() == 0 )
				return ;
			
			Boolean alreadyGotIdent = (Boolean) metaInfo.get(ALREADY_GOT_IDENT_CONST) ;
			if( alreadyGotIdent == null || !alreadyGotIdent  ){
				stringValues.clear() ;
				metaInfo.put(ALREADY_GOT_IDENT_CONST, true ) ;
			}			
			stringValues.add( pi ) ;
		}

		@Override
		protected Class<?>[] createNecessaryClasses() {
			return new Class[]{ ParsedIdent.class, ParsedString.class, ValueCluster.class};
		}

		@Override
		protected Class<?>[] createTargetClasses() {
			return new Class[]{ ParsedIdent.class, ParsedString.class, ValueCluster.class};
		}
	}
	
	private final static class OwnDocumentNameTraverser extends OwnAbstractTraverser<String>{
		

		private ArrayList<String> stringValues = new ArrayList<String>() ;
		
		public ArrayList<String> getStringValues(){
			
			if( stringValues.isEmpty() )
				return null ;
			
			ArrayList<String> result = new ArrayList<String>( stringValues ) ;
			stringValues.clear() ;
			return result ;
		}

		@Override
		protected void process( IProperty prop, Object val, Map<String, Object> metaInfo, int index ) {
			
			if( val instanceof String )
			{
				String str = (String) val ;
				String words[] = str.split("[ _-]|\\.") ;
				for( String s : words )	{
					s = s.trim() ;
					
					if(s.length() < 2)
						continue ;
					
					if( s.replaceAll("[0-9]", "").length() == 0 )
						continue ;
					
					if( s.replaceAll("[IVX]", "").length() == 0 )
						continue ;
					
					stringValues.add( s ) ;
				}
			}
		}

		@Override
		protected Class<?>[] createNecessaryClasses() {
			return new Class[]{ String.class };
		}

		@Override
		protected Class<?>[] createTargetClasses() {
			return new Class[]{ String.class };
		}
	}
	
	private final static class OwnParentCategoriesTraverser extends OwnAbstractTraverser<String>{
		

		private ArrayList<String> stringValues = new ArrayList<String>() ;
		
		public ArrayList<String> getStringValues(){
			
			if( stringValues.isEmpty() )
				return null ;
			
			ArrayList<String> result = new ArrayList<String>( stringValues ) ;
			stringValues.clear() ;
			return result ;
		}

		@Override
		protected void process( IProperty prop, Object val, Map<String, Object> metaInfo, int index ) {
			
			if( val instanceof ParsedLinkArray )
			{
				ParsedLinkArray la = (ParsedLinkArray) val ;
				for( String str : la.getLinksArray()  )
				{
					if( str.startsWith("[[") )
						str = str.substring(2).trim() ;
					
					if( str.endsWith("]]") )
						str = str.substring(0, str.length()-2).trim() ;
					
					stringValues.add( str ) ;						
				}					
			}
		}

		@Override
		protected Class<?>[] createNecessaryClasses() {
			return new Class[]{ ParsedLinkArray.class };
		}

		@Override
		protected Class<?>[] createTargetClasses() {
			return new Class[]{ ParsedLinkArray.class };
		}
	}
	

	@Override
	public Set<String> getRequiredAudits() {
		// TODO Auto-generated method stub
		return null;
	}

}
